package com.lin.maker.template;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.lin.maker.meta.Enum.FileGenerateTypeEnum;
import com.lin.maker.meta.Enum.FileTypeEnum;
import com.lin.maker.meta.Meta;
import com.lin.maker.model.TemplateMakerConfig;
import com.lin.maker.template.Enum.FileFilterRangeEnum;
import com.lin.maker.template.Enum.FileFilterRuleEnum;
import com.lin.maker.template.model.FileFilterConfig;
import com.lin.maker.template.model.TemplateMakerFileConfig;
import com.lin.maker.template.model.TemplateMakerModelConfig;
import com.lin.maker.template.model.TemplateMakerOutputConfig;
import lombok.Data;

import java.io.File;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;

public class TemplateMarker {
    /**
     * 制作模板
     * @param originProjectPath
     * @param templateMakerFileConfig
     * @param templateMakerModelConfig
     * @param meta
     * @param id
     * @return
     */
    protected static long makeTemplate(
            String originProjectPath,
            TemplateMakerFileConfig templateMakerFileConfig,
            TemplateMakerModelConfig templateMakerModelConfig,
            TemplateMakerOutputConfig templateMakerOutputConfig,
            Meta meta, Long id){
        if(id == null){
            id = IdUtil.getSnowflakeNextId();
        }
        String projectPath = System.getProperty("user.dir");
        String tempDirPath = projectPath + File.separator + ".temp";
        String tempplatePath = tempDirPath + File.separator + id;
        //复制目录
        if(!FileUtil.exist(tempplatePath)){
            FileUtil.mkdir(tempDirPath);
            FileUtil.copy(originProjectPath,tempplatePath,true);
        }
        // 2：输入文件
        /**
         * 根据我们给的tempplatePath（D:\Code-Generate\code-generate\lin-code-generate-maker\.temp\id)
         * 获取\id下的第一层目录 springboot-init-master
         * loopFiles的第二个参数就是指定文件遍历的深度
         * filter 表示我们只需要文件夹
         * findFirst 表示我们只需要第一个文件夹
         * getAbsolutePath 获取文件夹的绝对路径
         */
        String sourceRootPath = FileUtil.loopFiles(new File(tempplatePath),1,null)
                .stream()
                .filter(file -> file.isDirectory())
                .findFirst()
                .orElseThrow()
                .getAbsolutePath();

        //FileUtil.getLastPathEle() 方法根据其名字，应该是获取路径的最后一个元素。在这个例子中，acm-template 是 originProjectPath 路径的最后一部分
        // 注意 win 系统需要对路径进行转义
        sourceRootPath = sourceRootPath.replaceAll("\\\\", "/");


        List<Meta.FileConfigDTO.FileInfo> newFileInfoList = makeFileTemplates(templateMakerFileConfig, templateMakerModelConfig, sourceRootPath);

        List<Meta.ModelConfigDTO.ModelInfo> newModelInfoList = makeModelTemplates(templateMakerModelConfig);



        // 三： 生成元信息配置文件
        String metaOutputPath = tempplatePath + File.separator + "meta.json";

        if(FileUtil.exist(metaOutputPath)){
            // 说明meta.json存在
            Meta oldMeta = JSONUtil.toBean(FileUtil.readUtf8String(metaOutputPath), Meta.class);
            List<Meta.FileConfigDTO.FileInfo> filesInfoList = oldMeta.getFileConfig().getFiles();
            meta = oldMeta;
            filesInfoList.addAll(newFileInfoList);
            List<Meta.ModelConfigDTO.ModelInfo> modelInfoList = oldMeta.getModelConfig().getModels();
            modelInfoList.addAll(newModelInfoList);
            //追加配置之后去重
            oldMeta.getFileConfig().setFiles(distinctFiles(filesInfoList));
            oldMeta.getModelConfig().setModels(distinctModels(modelInfoList));
        }else{
            // 说明meta.json不存在
            // 构造meta信息得文件配置
            Meta.FileConfigDTO fileConfig = new Meta.FileConfigDTO();
            meta.setFileConfig(fileConfig);
            fileConfig.setSourceRootPath(sourceRootPath);
            List<Meta.FileConfigDTO.FileInfo> filesInfoList = new ArrayList<>(newFileInfoList);
            fileConfig.setFiles(filesInfoList);
            // 构造meta信息得数据模型配置
            Meta.ModelConfigDTO modelConfig = new Meta.ModelConfigDTO();
            meta.setModelConfig(modelConfig);
            List<Meta.ModelConfigDTO.ModelInfo> modelInfoList = new ArrayList<>(newModelInfoList);
            modelConfig.setModels(modelInfoList);
        }
//         额外的输出配置
        if(templateMakerOutputConfig != null){
            if(templateMakerOutputConfig.isRemoveGroupFilesFromRoot()){
                List<Meta.FileConfigDTO.FileInfo> files = meta.getFileConfig().getFiles();
                meta.getFileConfig().setFiles(TemplateMarkerUtils.removeGroupFilesFromRoot(files));
            }
        }
        FileUtil.writeUtf8String(JSONUtil.toJsonPrettyStr(meta),metaOutputPath);
        return id;
    }

    /**
     * 生成模型参数
     * @param templateMakerModelConfig
     * @return
     */
    private static List<Meta.ModelConfigDTO.ModelInfo> makeModelTemplates(TemplateMakerModelConfig templateMakerModelConfig) {
        List<Meta.ModelConfigDTO.ModelInfo> newModelInfoList = new ArrayList<>();
        if(templateMakerModelConfig == null){
            return newModelInfoList;
        }
        // 处理模型信息
        List<TemplateMakerModelConfig.ModelInfoConfig> models = templateMakerModelConfig.getModels();
        if(CollectionUtil.isEmpty(models)){
            return newModelInfoList;
        }
        List<Meta.ModelConfigDTO.ModelInfo> inputModelInfoList  = models.stream()
                .map(modelInfoConfig -> {
                    Meta.ModelConfigDTO.ModelInfo modelInfo = new Meta.ModelConfigDTO.ModelInfo();
                    BeanUtil.copyProperties(modelInfoConfig, modelInfo);
                    return modelInfo;
                }).collect(Collectors.toList());
        // 模型分组
        TemplateMakerModelConfig.ModelGroupConfig modelGroupConfig = templateMakerModelConfig.getModelGroupConfig();
        if(modelGroupConfig != null){
            Meta.ModelConfigDTO.ModelInfo modelInfo = new Meta.ModelConfigDTO.ModelInfo();
            BeanUtil.copyProperties(modelGroupConfig, modelInfo);
            //把用户这一次添加的代码先添加到这个分组中
            modelInfo.setModels(inputModelInfoList);
            // 再把这个
            newModelInfoList.add(modelInfo);
        }else {
            //不分组
            newModelInfoList.addAll(inputModelInfoList);
        }
        return newModelInfoList;
    }

    /**
     * 生成模板文件
     * @param templateMakerFileConfig
     * @param templateMakerModelConfig
     * @param sourceRootPath
     * @return
     */
    private static List<Meta.FileConfigDTO.FileInfo> makeFileTemplates(TemplateMakerFileConfig templateMakerFileConfig,
                                                                       TemplateMakerModelConfig templateMakerModelConfig,
                                                                       String sourceRootPath) {
        // 生成模板文件
        // 如果是目录 则遍历生成
        List<Meta.FileConfigDTO.FileInfo> newFileInfoList = new ArrayList<>();
        if(templateMakerFileConfig == null){
            return newFileInfoList;
        }
        List<TemplateMakerFileConfig.FileInfoConfig> fileConfigInfoList = templateMakerFileConfig.getFiles();
        if(CollUtil.isEmpty(fileConfigInfoList)){
            return newFileInfoList;
        }
        for (TemplateMakerFileConfig.FileInfoConfig fileInfoConfig : fileConfigInfoList) {
            String inputPath = fileInfoConfig.getPath();
            String inputFileAbsolutePath = sourceRootPath + File.separator + inputPath;
            List<File> files = FileFilter.doFilter(inputFileAbsolutePath, fileInfoConfig.getFilterConfigs());
            files = files.stream()
                    .filter(file -> !file.getAbsolutePath().endsWith("ftl"))
                    .toList();
            //经过过滤之后得文件不可能再是文件夹了
            for (File file : files) {
                Meta.FileConfigDTO.FileInfo fileInfo = makeFileTemplate(templateMakerModelConfig, sourceRootPath, file,fileInfoConfig);
                newFileInfoList.add(fileInfo);
            }
        }
        // 文件分组
        TemplateMakerFileConfig.FileGroupConfig fileGroupConfig = templateMakerFileConfig.getFileGroupConfig();
        if(fileGroupConfig != null){
            String groupName = fileGroupConfig.getGroupName();
            String groupKey = fileGroupConfig.getGroupKey();
            String groupCondition = fileGroupConfig.getGroupCondition();
            Meta.FileConfigDTO.FileInfo fileInfo = new Meta.FileConfigDTO.FileInfo();
            fileInfo.setGroupKey(groupKey);
            fileInfo.setCondition(groupCondition);
            fileInfo.setGroupName(groupName);
            fileInfo.setType(FileTypeEnum.GROUP.getValue());
            //把用户这一次添加的代码先添加到这个分组中
            fileInfo.setFiles(newFileInfoList);
            //添加之后重新刷新一下这个newFileInfoList
            newFileInfoList = new ArrayList<>();
            // 再把这个分组对象再塞回来
            newFileInfoList.add(fileInfo);
        }
        return newFileInfoList;
    }

    /**
     * 制作文件模板
     * @param templateMakerModelConfig
     * @param sourceRootPath
     * @param inputFile
     * @param fileInfoConfig
     * @return
     */
    private static Meta.FileConfigDTO.FileInfo makeFileTemplate(TemplateMakerModelConfig templateMakerModelConfig,
                                                                String sourceRootPath, File inputFile,
                                                                TemplateMakerFileConfig.FileInfoConfig fileInfoConfig){
        // 要挖坑的文件路径 ： 比如说我们之前是给MainTemplate.java 文件挖坑，生成MainTemplate.java.ftl文件，这里的inputFile就是MainTemplate.java
        String inputFileAbsolutePath = inputFile.getAbsolutePath().replaceAll("\\\\", "/");
        String outputFileAbsolutePath = inputFileAbsolutePath + ".ftl";
        // 构造这个文件的输入和输出路径
        // 文件输入输出相对路径（用于生成配置）
        String fileInputPath = inputFileAbsolutePath.replace(sourceRootPath + "/", "");
        String fileOutputPath = fileInputPath + ".ftl";
        // 使用字符串替换模板文件
        String fileContent = null;
        String replacement = null;
        boolean hasTemplateFile = FileUtil.exist(outputFileAbsolutePath);
        if(hasTemplateFile){
            // 之前存在输出路径
            fileContent = FileUtil.readUtf8String(outputFileAbsolutePath);
        }else {
            // 不存在则读取输入路径
            fileContent = FileUtil.readUtf8String(inputFileAbsolutePath);
        }
        String newContent = fileContent;
        TemplateMakerModelConfig.ModelGroupConfig modelGroupConfig = templateMakerModelConfig.getModelGroupConfig();
        // 支持多个模型：对同一个文件的内容，遍历模型进行多轮替换
        for (TemplateMakerModelConfig.ModelInfoConfig modelInfoConfig  : templateMakerModelConfig.getModels()) {
            if(modelGroupConfig == null){
                // 说明没有分组
                replacement = String.format("${%s}", modelInfoConfig.getFieldName());
            }else{
                replacement = String.format("${%s.%s}", modelGroupConfig.getGroupKey(), modelInfoConfig.getFieldName());
            }
            newContent = StrUtil.replace(newContent,modelInfoConfig.getReplaceText(),replacement);
        }
        // 构造meta.json中的文件配置
        Meta.FileConfigDTO.FileInfo fileInfo = new Meta.FileConfigDTO.FileInfo();
        // 进行一个调换，这里是制作模板得方法，我们制作出来的应该和之前代码生成器的元信息保持一致  （直接去看这个项目路径下的meta.json即可）
        fileInfo.setInputPath(fileOutputPath);
        fileInfo.setOutputPath(fileInputPath);
        fileInfo.setType(FileTypeEnum.FILE.getValue());
        fileInfo.setCondition(fileInfoConfig.getCondition());
        fileInfo.setGenerateType(FileGenerateTypeEnum.DYNAMIC.getValue());


        boolean contentEqual = newContent.equals(fileContent);
        //和原文件一样，则直接静态复制
        // ftl 文件如果存在说明就是动态文件   这里得hasTemplateFile就是代表以ftl为后缀得文件是否存在
        if(!hasTemplateFile){
            if(contentEqual){
                fileInfo.setInputPath(fileInputPath);
                fileInfo.setGenerateType(FileGenerateTypeEnum.STATIC.getValue());
            }else {
                // 不一样则写入文件
                FileUtil.writeUtf8String(newContent,outputFileAbsolutePath);
            }
        }else if(!contentEqual){
            FileUtil.writeUtf8String(newContent,outputFileAbsolutePath);
        }
        return fileInfo;
    }

    /**
     * 文件去重
     *
     * @param fileInfoList
     * @return
     */
    private static List<Meta.FileConfigDTO.FileInfo> distinctFiles(List<Meta.FileConfigDTO.FileInfo> fileInfoList) {
        //将所有文件配置（fileInfo）分为有分组的和无分组的
        Map<String, List<Meta.FileConfigDTO.FileInfo>> groupKeyFileInfoListMap = fileInfoList.stream()
                .filter(fileInfo -> StrUtil.isNotBlank(fileInfo.getGroupKey()))
                .collect(
                        Collectors.groupingBy(Meta.FileConfigDTO.FileInfo::getGroupKey)
                );
        // 这里的map大概是 ：
        // {"groupKey":"a",file:[[1,2],[2,3]]}
        // {"groupKey":"b",file:[[4,5]]}
        //对于有分组的文件配置，如果有相同的分组，同分组内的文件进行合并（merge），不同分组可同时保留
        // 我们现在需要把{"groupKey":"a",file:[[1,2],[2,3]]} 变成{"groupKey":"a",file:[[1,2,3]]} 并且装到newMap里面去
        Map<String, Meta.FileConfigDTO.FileInfo> newMap = new HashMap<>();
        for (Map.Entry<String, List<Meta.FileConfigDTO.FileInfo>> entry : groupKeyFileInfoListMap.entrySet()) {
            List<Meta.FileConfigDTO.FileInfo> tempFileInfoList  = entry.getValue();
            ArrayList<Meta.FileConfigDTO.FileInfo> newFileInfo = new ArrayList<>(tempFileInfoList.stream()
                    .flatMap(fileInfo -> fileInfo.getFiles().stream())
                    .collect(
                            Collectors.toMap(Meta.FileConfigDTO.FileInfo::getOutputPath, o -> o, (e, r) -> r)
                    ).values());
            // 使用新的 group 配置 新的配置在我们newFileInfoList的最后一个
            Meta.FileConfigDTO.FileInfo last = CollUtil.getLast(tempFileInfoList);
            last.setFiles(newFileInfo);
            newMap.put(entry.getKey(), last);
        }
        //创建新的文件配置列表（结果列表），先将合并后的分组添加到结果列表
        List<Meta.FileConfigDTO.FileInfo> resultfileInfoList = new ArrayList<>(newMap.values());
        //再将无分组的文件配置列表添加到结果列表
        List<Meta.FileConfigDTO.FileInfo> noGroupFileInfoList = fileInfoList.stream().filter(fileInfo -> StrUtil.isBlank(fileInfo.getGroupKey()))
                .toList();
        resultfileInfoList.addAll(new ArrayList<>(noGroupFileInfoList.stream()
                .collect(
                        Collectors.toMap(Meta.FileConfigDTO.FileInfo::getInputPath, o -> o, (e, r) -> r)
                ).values()));
        return resultfileInfoList;
        //Collectors.toMap() 方法
        //Collectors.toMap() 是一个用于将流中的元素收集到 Map 中的工厂方法，接收三个参数：
        //第一个参数：键映射函数，用于从流中的元素提取出 Map 的键。 这里的键就是fileInfo中的输入路径
        //第二个参数：值映射函数，用于从流中的元素提取出 Map 的值。 这里的o -> o 就是原封不动返回回来
        //第三个参数：合并函数，当键重复时用于合并值。             这里的 (e, r) -> r 就是保留新的值,比如，我 a：1 a: 2     最后保留的是2
    }
    /**
     * 模型去重
     *
     * @param modelInfoList
     * @return
     */
    private static List<Meta.ModelConfigDTO.ModelInfo> distinctModels(List<Meta.ModelConfigDTO.ModelInfo> modelInfoList) {
        //将所有文件配置（modelInfo）分为有分组的和无分组的
        Map<String, List<Meta.ModelConfigDTO.ModelInfo>> groupKeyModelInfoListMap = modelInfoList.stream()
                .filter(modelInfo -> StrUtil.isNotBlank(modelInfo.getGroupKey()))
                .collect(
                        Collectors.groupingBy(Meta.ModelConfigDTO.ModelInfo::getGroupKey)
                );
        // 这里的map大概是 ：
        // {"groupKey":"a",model:[[1,2],[2,3]]}
        // {"groupKey":"b",model:[[4,5]]}
        //对于有分组的文件配置，如果有相同的分组，同分组内的文件进行合并（merge），不同分组可同时保留
        // 我们现在需要把{"groupKey":"a",model:[[1,2],[2,3]]} 变成{"groupKey":"a",model:[[1,2,3]]} 并且装到newMap里面去
        Map<String, Meta.ModelConfigDTO.ModelInfo> newMap = new HashMap<>();
        for (Map.Entry<String, List<Meta.ModelConfigDTO.ModelInfo>> entry : groupKeyModelInfoListMap.entrySet()) {
            List<Meta.ModelConfigDTO.ModelInfo> tempModelInfoList  = entry.getValue();
            ArrayList<Meta.ModelConfigDTO.ModelInfo> newModelInfo = new ArrayList<>(tempModelInfoList.stream()
                    .flatMap(modelInfo -> modelInfo.getModels().stream())
                    .collect(
                            Collectors.toMap(Meta.ModelConfigDTO.ModelInfo::getFieldName, o -> o, (e, r) -> r)
                    ).values());
            // 使用新的 group 配置 新的配置在我们newModelInfoList的最后一个
            Meta.ModelConfigDTO.ModelInfo last = CollUtil.getLast(tempModelInfoList);
            last.setModels(newModelInfo);
            newMap.put(entry.getKey(), last);
        }
        //创建新的文件配置列表（结果列表），先将合并后的分组添加到结果列表
        List<Meta.ModelConfigDTO.ModelInfo> resultmodelInfoList = new ArrayList<>(newMap.values());
        //再将无分组的文件配置列表添加到结果列表
        List<Meta.ModelConfigDTO.ModelInfo> noGroupModelInfoList = modelInfoList.stream().filter(fileInfo -> StrUtil.isBlank(fileInfo.getGroupKey()))
                .toList();
        resultmodelInfoList.addAll(new ArrayList<>(noGroupModelInfoList.stream().collect(
                Collectors.toMap(Meta.ModelConfigDTO.ModelInfo::getFieldName, o -> o, (e, r) -> r)
        ).values()));
        return resultmodelInfoList;
    }




    /**
     * 制作模板
     *
     * @param templateMakerConfig
     * @return
     */
    public static long makeTemplate(TemplateMakerConfig templateMakerConfig) {
        Meta meta = templateMakerConfig.getMeta();
        String originProjectPath = templateMakerConfig.getOriginProjectPath();
        TemplateMakerFileConfig templateMakerFileConfig = templateMakerConfig.getFileConfig();
        TemplateMakerModelConfig templateMakerModelConfig = templateMakerConfig.getModelConfig();
        TemplateMakerOutputConfig templateMakerOutputConfig = new TemplateMakerOutputConfig();
        Long id = templateMakerConfig.getId();

        return makeTemplate(originProjectPath, templateMakerFileConfig, templateMakerModelConfig,templateMakerOutputConfig,meta,id);
    }

}
